package workflow;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import workflow.core.WorkflowBlock;
import workflow.core.WorkflowElement;
import workflow.dto.WorkflowConnection;
import workflow.dto.WorkflowMessage;
import workflow.dto.WorkflowProcess;
import workflow.dto.WorkflowProcessDefinition;
import workflow.others.MessageType;


public class WorkflowEngine {
	
	/** procesy zaladowane na silnik */
	private Map<String, WorkflowProcess> engineProcesses;
	
	public WorkflowEngine() {
		engineProcesses = new HashMap<String, WorkflowProcess>();
	}
	
	/** laduje procesy na silnik */
	public void loadProcesses(List<WorkflowProcess> processes){
		for(WorkflowProcess process : processes){
			deploy(process);
		}
	}
	
	/** wrzuca kopie procesu na silnik. true jesli sie udalo */
	public boolean deploy(WorkflowProcess process){
		String key = process.getName() + process.getVersion();
		if(engineProcesses.get(key) == null){
			WorkflowProcess prc = process.getCopy();
			// na silniku wszystkie definicje procesow musza miec zainicjalizowane parametry
			// robie to profilaktycznie
			prc.getDefinition().initizalizeElementsParameters();
			engineProcesses.put(key, prc);
			return true;
		}
		return false;
	}
	
	/** startuje proces o podanej definicji. dziala na przekazanej definicji */
	public WorkflowMessage startProcess(WorkflowProcessDefinition def, WorkflowMessage inputMessage, boolean runAsynchronous){
		// TODO: proces wolany asynchronicznie musi byc inaczej obsluzony, 
		//silnik powinien przechwoywac rezultat dzialania takiego procesu
		
		/* 1. pobieram WorkflowConnection o source = START
		 * 2. pobieram jego target
		 * 3. setuje mu inMessages
		 * 4. wolam excecute, zapisuje wynik int
		 * 5. pobieram outMessages
		 * 6. pobieram WorkflowConnection o source = nazwa_wykonanego_bloku
		 * 7. w zaleznosci od wyniku int lece do odpowiedniego targeta
		 * 8. przechodze do pkt 4 jesli target != END
		 * 9. jesli taget = END zwracam WorkflowMessage
		 */
		
	    WorkflowConnection conn = def.getConnectionBySource("START");
	    String blockName = conn.getTarget(0);
	    
	    // ustawiam parametry wejsciowe
	    List<WorkflowMessage> messages = new ArrayList<WorkflowMessage>();
	    messages.add(inputMessage);
	    
	    while(!blockName.equals("END")){
	    	// poobieram blok i ustawiam mu wiadomosci wejsciowe
	    	WorkflowBlock block = def.getWorkflowBlockByName(blockName);
			block.setInMessages(messages);
			
			// wykonuje na nim akcje
			int result = block.excecute();
			
			// kazdej wiadomosci wyjsciowej ustawiam nazwe bloku z ktorego pochodzi
			for(WorkflowMessage message : block.getOutMessages()){
				message.setBlockName(blockName);
			}
			
			// setuje nowe wiadomosci wejsciowe dla kolejnej iteracji
			messages = new ArrayList<WorkflowMessage>();
			messages.addAll(block.getOutMessages());
			
			// szukam nastepnego bloku
			conn = def.getConnectionBySource(blockName);
			blockName = conn.getTarget(result);
	    }
		// zwracana jest tylko jedna wiadomosc wyjsciowa
	    // uzytkownik powinien zadbac o merge jesli bylo ich wiecej
		return messages.get(0);
	}
	
	/** startuje proces korzystajac z definicji zaladowanej na silnik.
	 * 	dla processVersion = 0 startuje najnowsza wersje procesu */
	public WorkflowMessage startProcess(String processName, int processVersion, WorkflowMessage inputMessage, boolean runAsynchronous){
		WorkflowProcess process = getProcess(processName, processVersion, false);
		if(process != null){
			return startProcess(process.getDefinition(), inputMessage, runAsynchronous);
		}else{
			// zwracam wiadomosc zawierajaca opis bledu
			WorkflowMessage message = new WorkflowMessage(getClass(), MessageType.WRONG);
			message.setErrorDescription("Cannot find process with processName="+processName+
					"and processVersion="+processVersion);
			return message;
		}
	}
	
	/** pobiera kopie procesu z silnika. 
	 * 	dla processVersion = 0 pobiera najnowsza wersje */
	public WorkflowProcess getProcess(String processName, int processVersion){
		return getProcess(processName, processVersion, true);
	}
	
	/** pobiera proces z silnika. parametr copy decyduje czy dostajemy kopie czy oryginalny element
	 * 	dla processVersion = 0 pobiera najnowsza wersje */
	private WorkflowProcess getProcess(String processName, int processVersion, boolean copy){
		WorkflowProcess process;
		String key;
		if(processVersion != 0){
			key = processName + processVersion;
		}else{
			int version = 1;
			key = processName + version;
			// wersja 1 musi istniec zeby mozna bylo szukac najnowszej
			process = engineProcesses.get(key);
			if(process != null ){
				// teraz szukam najnowszej wersji
				while(process != null){
					version++;
					key = processName + version;
					process = engineProcesses.get(key);
				}
				version--;
				key = processName + version;
			}else{
				// nie istnieje wersja 1 wiec zwracam null
				return null;
			}
		}
		if(copy){
			return engineProcesses.get(key).getCopy();
		}else{
			return engineProcesses.get(key);
		}
	}
	
	/** usuwa proces z silnika. 
	 * 	zwraca true jesli znalezniono i usunieto element */
	boolean deleteProcess(String processName, int processVersion){
		WorkflowProcess process = getProcess(processName, processVersion, false);
		if(process != null){
			String key = process.getName()+process.getVersion();
			engineProcesses.remove(key);
			return true;
		}
		return false;
	}
	
}
